/**
 * 201126:增加线与球的相交检测
 * 210824：升级修改【three.js r120-r131】
 * 220104：新增倒角方法（bevelCorner）
 * 20220304:[require] to [import]...jordia
 */
import {Vector3,Ray,Sphere,Line3,Quaternion,Plane,BufferGeometry,Vector2} from 'three';
import Utils from "./utils";

const MAX_FLOAT=1e12;
const MIN_FLOAT=1e-12;
const ZERO_VEC=new Vector3(0,0,0);

let tV0=new Vector3();
let tV1=new Vector3();
let tV2=new Vector3();
let tV3=new Vector3();
let tV4=new Vector3();
let tV5=new Vector3();
let tV6=new Vector3();
let tV7=new Vector3();
let tV8=new Vector3();
let tA0=[];
let tR0=new Ray();
let tS0=new Sphere();
let tL0=new Line3();
let tQ0=new Quaternion();
let tQ1=new Quaternion();

export default class GeometryUtils{
    constructor(){
        console.warn('do not instantiate class GeometryUtils,use the static method provided!');
    }

    /**
     * 倒角
     * @param p0 {Vector3} 角起始点
     * @param p1 {Vector3} 角顶点
     * @param p2 {Vector3} 角结束点
     * @param length {number} 倒角长度（与角两边比较取最小值）
     * @param segments {number} 倒角段数
     * @param out {Array} 输出倒角点
     * @returns {Array}
     */
    static bevelCorner(p0,p1,p2,length=0.1,segments=5,out=[]){
        let ls01=p0.distanceToSquared(p1);
        let ls12=p1.distanceToSquared(p2);
        let len=Math.sqrt(Math.min(ls01,ls12,length*length));
        let d10=tV0.subVectors(p0,p1).normalize();
        let d01=tV1.subVectors(p1,p0).normalize();
        let d12=tV2.subVectors(p2,p1).normalize();
        let rot=tQ0.setFromUnitVectors(d01,d12);
        let ang=d10.angleTo(d12)*0.5;
        let le=len/Math.cos(ang);
        d10.add(d12).normalize();
        let center=d10.multiplyScalar(le).add(p1);
        let sp=tV3.subVectors(p0,p1).normalize().multiplyScalar(len).add(p1);
        let ds=tV4.subVectors(sp,center);
        let ut=1/(segments-1);
        let tQ=tQ1;
        for(let i=0;i<segments;i++){
            let t=ut*i;
            tQ.identity();
            tQ.slerp(rot,t);
            let p=ds.clone().applyQuaternion(tQ);
            p.add(center);
            out.push(p);
        }
        return out;
    }

    /**
     * 三点是否共线（未测试）
     * @param pStart
     * @param pEnd
     * @param pTarget
     * @param isSegment
     * @param marginThreshold
     * @param distanceThreshold
     * @returns {boolean}
     */
    static checkCollinear(pStart,pEnd,pTarget,isSegment=false,marginThreshold=0,distanceThreshold=MIN_FLOAT) {
        tV0.subVectors(pEnd, pStart);
        tV1.subVectors(pTarget, pStart);
        tV1.projectOnVector(tV0).add(pStart);
        let dis=pTarget.distanceToSquared(tV1);
        if(!isSegment){
            return dis<=distanceThreshold;
        }else {
            if (dis > distanceThreshold) return false;
            dis = pStart.distanceToSquared(pEnd);
            let dT = Math.max(tV1.distanceToSquared(pStart), tV1.distanceToSquared(pEnd));
            return dT < dis + marginThreshold;
        }
    }

    /**
     * 计算线段在平面上的投影线段
     * @param startIn
     * @param endIn
     * @param startOut
     * @param endOut
     * @param planeNormal
     * @param constant
     */
    static computeProjectLineOnPlane(startIn,endIn,startOut,endOut, planeNormal, constant) {
        let offset = new Vector3().copy(planeNormal).multiplyScalar(constant);
        startOut.copy(startIn);
        endOut.copy(endIn);
        startOut.projectOnPlane(planeNormal).add(offset);
        endOut.projectOnPlane(planeNormal).add(offset);
    }

    /**
     * 计算点序列的平均法线
     * @param points
     * @param normalOut
     */
    static computeAverageNormalOfPointSequence(points,normalOut) {
        normalOut.set(0,0,0);
        let normal =new Vector3();
        for (let i = 0; i < points.length - 2; i++) {
            GeometryUtils.computeThreePointNormal(points[i], points[i + 1], points[i + 2],normal);
            normalOut.add(normal);
        }
        GeometryUtils.computeThreePointNormal(points[points.length - 2], points[points.length - 1], points[0],normal);
        normalOut.add(normal);
        GeometryUtils.computeThreePointNormal(points[points.length - 1], points[0], points[1],normal);
        normalOut.add(normal);
        normalOut.normalize();
    }

    /**
     * 计算3点法线
     * @param p1
     * @param p2
     * @param p3
     * @param out
     * @returns {Vector3}
     */
    static computeThreePointNormal(p1,p2,p3,out){
        out.subVectors(p2, p1);
        let AC = new Vector3().subVectors(p3, p1);
        return out.cross(AC).normalize();
    }

    /**
     * 判断4点共面
     * @param p1
     * @param p2
     * @param p3
     * @param p4
     * @returns {boolean}
     */
    static checkCoplanar(p1, p2, p3, p4) {
        let plane=new Plane().setFromCoplanarPoints(p1,p2,p3);
        let n = plane.normal;
        let d = plane.constant;
        let nd = n.dot(p4);
        return nd===d;
    }

    /**
     * 两直线的公垂线
     * @param s1
     * @param e1
     * @param s2
     * @param e2
     * @param sOut
     * @param eOut
     * @param is3D
     */
    static closestPointsOf2Lines(s1,e1,s2,e2,sOut,eOut,is3D=true){
        let type=is3D?'vector3':'vector2';
        let p1 =s1;
        let d1 =tV0.subVectors(e1,s1).normalize();
        let p2 =s2;
        let d2 =tV1.subVectors(e2,s2).normalize();
        let crs = tV2.crossVectors(d1, d2);
        if (Utils.equal(crs,ZERO_VEC,type,MIN_FLOAT)) {
            sOut.copy(p1);
            eOut.copy(p2);
            return;
        }
        let v1a = tV3.subVectors(p2, p1);
        let v2a = tV4.copy(v1a);
        let v1b = v1a.cross(d2);
        let v1c = tV5.crossVectors(d1, d2);
        let v2c =v1c;
        let v1d = v1b.dot(v1c);
        let v1e = v1c.lengthSq();
        let t1 = v1d / v1e;
        let v2b = v2a.cross(d1);
        let v2d = v2b.dot(v2c);
        let v2e = v2c.lengthSq();
        let t2 = v2d / v2e;
        d1.multiplyScalar(t1).add(p1);
        d2.multiplyScalar(t2).add(p2);
        sOut.copy(d1);
        eOut.copy(d2);
    }

    /**
     * 射线到网格最近点的信息（同一坐标系）
     * @param ray
     * @param mesh
     * @param meshOut
     * @param rayOut
     * @returns {number}
     */
    static closestPointOfRayAndMesh(ray,mesh,meshOut,rayOut){
        let tmpMPoint =new Vector3();
        let tmpRPoint =new Vector3();
        let minDis=Number.POSITIVE_INFINITY;
        let faceBounder=[
            new Vector3(),
            new Vector3(),
            new Vector3(),
            new Vector3()
        ];
        let ai, bi, ci;
        if(mesh.geometry instanceof BufferGeometry) {
            let indexAttribute = mesh.geometry.index;
            let positions = mesh.geometry.getAttribute('position').array;
            if (indexAttribute) {
                let indices = indexAttribute.array;
                for (let i = 0; i < indices.length ; i += 3) {
                    ai = indices[i];
                    bi = indices[i + 1];
                    ci = indices[i + 2];
                    faceBounder[3].set(positions[ai * 3], positions[ai * 3 + 1], positions[ai * 3 + 2]);
                    faceBounder[0].set(positions[ai * 3], positions[ai * 3 + 1], positions[ai * 3 + 2]);
                    faceBounder[1].set(positions[bi * 3], positions[bi * 3 + 1], positions[bi * 3 + 2]);
                    faceBounder[2].set(positions[ci * 3], positions[ci * 3 + 1], positions[ci * 3 + 2]);
                    let dis=GeometryUtils.distanceSqOfRayAndSpline(ray,faceBounder,tmpRPoint,tmpMPoint);
                    if (minDis>dis) {
                        minDis=dis;
                        if(meshOut)meshOut.copy(tmpMPoint);
                        if(rayOut)rayOut.copy(tmpRPoint);
                    }
                }
            } else {
                let indices = mesh.geometry.drawRange;
                for (let i = indices.start; i < indices.count / 3 - 2; i += 3) {
                    ai = i * 3;
                    bi = (i + 1) * 3;
                    ci = (i + 2) * 3;
                    faceBounder[3].set(positions[ai * 3], positions[ai * 3 + 1], positions[ai * 3 + 2]);
                    faceBounder[0].set(positions[ai * 3], positions[ai * 3 + 1], positions[ai * 3 + 2]);
                    faceBounder[1].set(positions[bi * 3], positions[bi * 3 + 1], positions[bi * 3 + 2]);
                    faceBounder[2].set(positions[ci * 3], positions[ci * 3 + 1], positions[ci * 3 + 2]);
                    let dis=GeometryUtils.distanceSqOfRayAndSpline(ray,faceBounder,tmpRPoint,tmpMPoint);
                    if (minDis>dis) {
                        minDis=dis;
                        if(meshOut)meshOut.copy(tmpMPoint);
                        if(rayOut)rayOut.copy(tmpRPoint);
                    }
                }
            }
        }
        // 20210824
        // else{
        //     for (let i = 0; i < mesh.geometry.faces.length; i++) {
        //         ai = mesh.geometry.faces[i].a;
        //         bi = mesh.geometry.faces[i].b;
        //         ci = mesh.geometry.faces[i].c;
        //         faceBounder[3].copy(mesh.geometry.vertices[ai]);
        //         faceBounder[0].copy(mesh.geometry.vertices[ai]);
        //         faceBounder[1].copy(mesh.geometry.vertices[bi]);
        //         faceBounder[2].copy(mesh.geometry.vertices[ci]);
        //         let dis = GeometryUtils.distanceSqOfRayAndSpline(ray,faceBounder,tmpRPoint,tmpMPoint);
        //         if (minDis>dis) {
        //             minDis=dis;
        //             if(meshOut)meshOut.copy(tmpMPoint);
        //             if(rayOut)rayOut.copy(tmpRPoint);
        //         }
        //     }
        // }
        return minDis;
    }

    /**
     * 射线到多段线的最近距离（平方）和点（可选）
     * @param ray
     * @param points
     * @param rayPointOut
     * @param spPointOut
     * @returns {number}
     */
    static distanceSqOfRayAndSpline(ray,points,rayPointOut,spPointOut){
        let minDis=Number.POSITIVE_INFINITY;
        let rp=new Vector3(),sp=new Vector3();
        for(let i=0;i<points.length-1;i++){
            let s=points[i],e=points[i+1];
            let ds=ray.distanceSqToSegment(s,e,rp,sp);
            if(minDis>ds){
                minDis=ds;
                if(rayPointOut)rayPointOut.copy(rp);
                if(spPointOut)spPointOut.copy(sp);
            }
        }
        return minDis;
    }

    /**
     * 代表任意线的点序列的比例分点
     */
    static dividePointOfPoints(ratio,points,out=new Vector3()){
        tA0.length=0;
        let lengths=GeometryUtils.pointsEveryLength(points,tA0);
        let length=lengths[lengths.length-1];
        let len=length*(ratio%1);
        for(let i=0;i<lengths.length;i++){
            if(lengths[i]===len){
                return out.copy(points[i]);
            }else if(lengths[i]>len&&lengths[i-1]<len){
                let rat=(lengths[i]-len)/(lengths[i]-lengths[i-1]);
                out.subVectors(points[i-1],points[i]);
                return out.multiplyScalar(rat).add(points[i]);
            }
        }
        return null;
    }

    /**
     * 线段与线段是否相交
     * @param l0s
     * @param l0e
     * @param l1s
     * @param l1e
     * @param is3D
     * @param precision
     * @returns {boolean}
     */
    static lineIntersectsLine(l0s,l0e,l1s,l1e,is3D=true,precision=MIN_FLOAT){
        let type=is3D?'vector3':'vector2';
        let a =l0s;
        let b =l0e;
        let c =l1s;
        let d =l1e;
        if (Utils.equal(a,c,type,precision) ||Utils.equal(a,d,type,precision))return false;
        if (Utils.equal(b,c,type,precision)||Utils.equal(b,d,type,precision))return false;
        if (GeometryUtils.checkCollinear(a, b, c,false,precision,precision) || GeometryUtils.checkCollinear(a, b, d,false,precision,precision))return false;
        if (GeometryUtils.checkCollinear(c, d, a,false,precision,precision) || GeometryUtils.checkCollinear(c, d, b,false,precision,precision))return false;
        let dir=tV0;
        let d1=tV1;
        let d2=tV2;
        let crs1=tV3;
        let crs2=tV4;
        dir.subVectors(b,a);
        d1.subVectors(c,a);
        d2.subVectors(d,a);
        crs1.crossVectors(d1, dir).normalize();
        crs2.crossVectors(d2, dir).normalize();
        if (!Utils.equal(crs1,crs2.negate(),type,precision))return false;
        dir.subVectors(d,c);
        d1.subVectors(a,c);
        d2.subVectors(b,c);
        crs1.crossVectors(d1, dir).normalize();
        crs2.crossVectors(d2, dir).normalize();
        return Utils.equal(crs1,crs2.negate(),type,precision);
    }

    /**
     * 线是否与球体相交
     */
    static lineIntersectsSphere(start,end,sphere,type=0,precision=MIN_FLOAT){
        tS0.copy(sphere).radius+=precision;
        tL0.start.copy(start);
        tL0.end.copy(end);
        if(tS0.containsPoint(start))return true;
        if(tS0.containsPoint(end))return true;
        switch (type) {
            case 0://line
                tL0.closestPointToPoint(tS0.center,false,tV6);
                return tS0.containsPoint(tV6);
            case 1://ray
                tL0.closestPointToPoint(tS0.center,false,tV6);
                if(!tS0.containsPoint(tV6))return false;
                tV7.subVectors(tV6,start);
                tV8.subVectors(end,start);
                return tV7.dot(tV8)>0;
            case 2://segment
                tL0.closestPointToPoint(tS0.center,true,tV6);
                return tS0.containsPoint(tV6);
        }
    }

    /**
     * 线段与线段是否有重叠（非相交）
     * @param s0
     * @param e0
     * @param s1
     * @param e1
     * @param angleThreshold
     * @param distanceThreshold
     * @param headThreshold
     * @returns {boolean}
     */
    static lineOverlapsLine(s0,e0,s1,e1,angleThreshold=MIN_FLOAT,distanceThreshold=MIN_FLOAT,headThreshold=0) {
        let dir = tV0.subVectors(e0, s0).normalize();
        let d = tV1.subVectors(e1, s1).normalize();
        if (Math.abs(dir.dot(d)) > angleThreshold) {
            if (GeometryUtils.checkCollinear(s0, e0,s1,  true, headThreshold, distanceThreshold)) return true;
            if (GeometryUtils.checkCollinear(s0, e0,e1,  true, headThreshold, distanceThreshold)) return true;
            if (GeometryUtils.checkCollinear(s1, e1,s0,  true, headThreshold, distanceThreshold)) return true;
            if (GeometryUtils.checkCollinear(s1, e1,e0,  true, headThreshold, distanceThreshold)) return true;
        }
        return false;
    }

    /**
     * 线是否与包围盒相交
     * @param pStart
     * @param pEnd
     * @param box
     * @param type
     * @param onlyBound
     * @returns {boolean}
     */
    static lineIntersectsBox(pStart,pEnd,box,type=0,onlyBound=false){
        if(type===0){
            /**
             * 直线
             */
            tR0.origin.copy(pStart);
            tR0.direction.subVectors(pEnd,pStart).normalize();
            let intP=tR0.intersectsBox(box);
            if(intP)return true;
            tR0.origin.copy(pEnd);
            tR0.direction.subVectors(pStart,pEnd).normalize();
            intP=tR0.intersectsBox(box);
            return intP;
        }else if(type===1){
            /**
             * 射线
             */
            tR0.origin.copy(pStart);
            tR0.direction.subVectors(pEnd,pStart).normalize();
            return tR0.intersectsBox(box);
        }else{
            /**
             * 线段
             */
            let cp0=box.containsPoint(pStart);
            let cp1=box.containsPoint(pEnd);
            if(cp0&&cp1){
                return !onlyBound;
            }else if(cp0||cp1){
                return true;
            }
            tR0.origin.copy(pStart);
            tR0.direction.subVectors(pEnd,pStart).normalize();
            let intP=tR0.intersectBox(box,tV0);
            if(!intP)return false;
            return GeometryUtils.checkCollinear(pStart,pEnd,intP.clone(),true);
        }
    }

    /**
     * 判断点是否在三角形内部（规范后未测试）
     * @param a {Vector2|Vector3} 三角形顶点
     * @param b {Vector2|Vector3} 三角形顶点
     * @param c {Vector2|Vector3} 三角形顶点
     * @param p {Vector2|Vector3} 检测点
     * @returns {boolean}
     */
    static ifPointInTriangle(a, b, c, p) {
        let isV3=a.isVector3;
        let v0 =isV3?new Vector3(): new Vector2();
        v0.subVectors(c, a);
        let v1 = isV3?new Vector3(): new Vector2();
        v1.subVectors(b, a);
        let v2 = isV3?new Vector3(): new Vector2();
        v2.subVectors(p, a);
        let d00, d01, d02, d11, d12;
        d00 = v0.dot(v0);
        d01 = v0.dot(v1);
        d02 = v0.dot(v2);
        d11 = v1.dot(v1);
        d12 = v1.dot(v2);
        let u = (d11 * d02 - d12 * d01) / (d00 * d11 - d01 * d01);
        if (u < 0 || u > 1)return false;
        let v = (d12 * d00 - d01 * d02) / (d00 * d11 - d01 * d01);
        if (v < 0 || v > 1)return false;
        return u + v <= 1;
    }

    /**
     * 直线与平面的夹角
     * @param lineStart
     * @param lineEnd
     * @param planeNormal
     * @param isVector {boolean} 是否视线段是无向的
     * @returns {number}
     */
    static lineAngleToPlane(lineStart,lineEnd, planeNormal, isVector=false) {
        let v = new Vector3().subVectors(lineEnd, lineStart);
        let angle = v.angleTo(planeNormal);
        angle = Math.PI / 2 - angle;
        if (!isVector) {
            angle = Math.abs(angle);
        }
        return angle;
    }

    /**
     * 创建2D包围盒（box2）
     * @param x0 {number}
     * @param x1 {number}
     * @param y0 {number}
     * @param y1 {number}
     * @param out {Box2}
     * @param minToMaxOrdered {boolean}
     */
    static make2DAABB(x0,x1,y0,y1,out,minToMaxOrdered=false) {
        let ix=minToMaxOrdered?x0:Math.min(x0,x1);
        let ax=minToMaxOrdered?x1:Math.max(x0,x1);
        let iy=minToMaxOrdered?y0:Math.min(y0,y1);
        let ay=minToMaxOrdered?y1:Math.max(y0,y1);
        out.min.set(ix,iy);
        out.max.set(ax,ay);
    }

    /**
     * 代表任意线的点序列的起点到各点的长度
     */
    static pointsEveryLength(points,out=[]) {
        let vertices = points;
        out.push(0);
        let tempLength = 0;
        for (let i = 1; i < vertices.length; i++) {
            tempLength += vertices[i - 1].distanceTo(vertices[i]);
            out.push(tempLength);
        }
        return out;
    }

    /**
     * 4点球
     * @param p1
     * @param p2
     * @param p3
     * @param p4
     * @param sphere
     */
    static sphereBy4Points(p1, p2, p3, p4,sphere) {
        if (GeometryUtils.checkCoplanar(p1, p2, p3, p4)) {
            sphere.center.set(0,0,0);
            sphere.radius=MAX_FLOAT;
            return;
        }
        let nom1 =new Vector3();
        GeometryUtils.computeThreePointNormal(p1, p2, p3,nom1);
        let nom2 =new Vector3();
        GeometryUtils.computeThreePointNormal(p2, p3, p4,nom2);
        let pnt1=new Vector3();
        GeometryUtils.triangleCircumcircle(p1,p2,p3,pnt1,null);
        let pnt2 =new Vector3();
        GeometryUtils.triangleCircumcircle(p2, p3, p4,pnt2,null);

        GeometryUtils.closestPointsOf2Lines(pnt1,nom1,pnt2,nom2,sphere.center,new Vector3(),true);
        sphere.radius = sphere.center.distanceTo(p1);
    }

    /**
     * B样条平滑多段线
     * @param points
     * @param delta
     * @param closed {boolean} 不是曲线是否闭合，而是使用闭合的算法还是不闭合的算法
     * @returns {[]}
     */
    static smoothPoints(points,delta=0.1,closed=false){
        let pntLen=points.length;
        let newPnts=[];
        let smooth=(i0,i1,i2,i3,out)=>{
            for (let t = 0.0; t <= 1.0; t += delta) {
                let a1 =  Math.pow((1 - t), 3) / 6;
                let a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
                let a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
                let a4 =  Math.pow(t, 3) / 6;
                let x = a1*points[i0].x + a2*points[i1].x + a3*points[i2].x + a4*points[i3].x;
                let y = a1*points[i0].y + a2*points[i1].y + a3*points[i2].y + a4*points[i3].y;
                out.push(new Vector3(x,y,0));
            }
        };
        if(closed){
            for (let n = 0; n < pntLen; n++) {
                if (n <= pntLen - 4) {
                    smooth(n,n+1,n+2,n+3,newPnts);
                } else if (n === pntLen - 3) {
                    smooth(n,n+1,n+2,0,newPnts);
                } else if (n === pntLen - 2) {
                    smooth(n,n+1,0,1,newPnts);
                } else if (n === pntLen - 1) {
                    smooth(n,0,1,2,newPnts);
                }
            }
        }else{
            newPnts.push(new Vector3(points[0].x,points[0].y,0));
            for (let n = 0; n <= pntLen-4; n++) {
                smooth(n, n + 1, n + 2, n + 3, newPnts);
            }
            newPnts.push(new Vector3(points[pntLen - 1].x, points[pntLen - 1].y,0));
        }

        return newPnts;
    }

    /**
     * 三角形外接圆
     * @param p1
     * @param p2
     * @param p3
     * @param centerOut
     * @param normalOut
     * @returns {number}
     */
    static triangleOuterCircle(p1, p2, p3,centerOut,normalOut) {
        let e1, e2, e3, d1, d2, d3, c1, c2, c3, c;
        e1 = new Vector3().subVectors(p3, p2);
        e2 = new Vector3().subVectors(p1, p3);
        e3 = new Vector3().subVectors(p2, p1);
        d1 = -(e2.dot(e3));
        d2 = -(e3.dot(e1));
        d3 = -(e1.dot(e2));
        c1 = d2 * d3;
        c2 = d3 * d1;
        c3 = d1 * d2;
        c = c1 + c2 + c3;
        c = (c === 0) ? (1 / MAX_FLOAT) : c;
        let v1 = new Vector3().copy(p1).multiplyScalar((c2 + c3) / (2 * c));
        let v2 = new Vector3().copy(p2).multiplyScalar((c3 + c1) / (2 * c));
        let v3 = new Vector3().copy(p3).multiplyScalar((c1 + c2) / (2 * c));
        if(centerOut)centerOut.addVectors(v1, v2).add(v3);
        let radius = Math.sqrt((d1 + d2) * (d2 + d3) * (d3 + d1) / c) / 2;
        if(normalOut)GeometryUtils.computeThreePointNormal(p1,p2,p3,normalOut);
        return radius;
    }
    static triangleCircumcircle(){
        console.error('renamed to triangleOuterCircle');
    }
}